package name.cyrax.red.jpv.impl.funcs;

import name.cyrax.red.jpv.api.adapters.CalculationContext;
import name.cyrax.red.jpv.api.adapters.DoubleAdapter;
import name.cyrax.red.jpv.api.adapters.IntegerAdapter;
import name.cyrax.red.jpv.api.adapters.LongAdapter;
import name.cyrax.red.jpv.api.adapters.NumericAccessor;
import name.cyrax.red.jpv.api.adapters.NumericAdapter;
import name.cyrax.red.jpv.api.funcs.CompiledInvocable;
import name.cyrax.red.jpv.api.funcs.FunctorInstance;
import name.cyrax.red.jpv.api.funcs.Invocable;
import name.cyrax.red.jpv.api.funcs.InvocationContext;
import name.cyrax.red.jpv.api.funcs.info.FunctorMethod;
import name.cyrax.red.jpv.api.funcs.info.ValueType;
import name.cyrax.red.jpv.api.vectors.Vector;
import name.cyrax.red.jpv.api.vectors.VectorFactory;
import name.cyrax.red.jpv.api.vectors.VectorWriter;

class CompiledInvocableImpl<N extends Number> implements Invocable<N>, CompiledInvocable<N> {

	private final Invocable<N>	invocable;
	private final InvocationContext parentContext;
	private final InvocationContext callingContext;
	private final NumericAdapter adapter;
	
	CompiledInvocableImpl(Invocable<N> invocableImpl, 
			InvocationContext parentContext, NumericAdapter adapter,
			InvocationContext callingContext) 
	{
		this.invocable = invocableImpl;
		this.adapter = adapter;
		this.parentContext = parentContext;
		this.callingContext = callingContext;
	}
	
	@Override public boolean isCompilationContext(InvocationContext context) {
		return context == parentContext || context == callingContext;
	}
	
	@Override public synchronized Vector<N> invoke()
	{
		callingContext.beginCalc();
		try {
			VectorWriter<N> w = this.invocable.factory().newInstance(adapter.size());
			CalculationContext c = callingContext.getCalculationContext();
			c.getMapReduceFactory().map(adapter, w);
			return w.done();				
		} finally {
			callingContext.endCalc();
		}
	}

	@Override public CompiledInvocable<N> compile()
	{
		return this;
	}

	@Override public VectorFactory<N> factory()
	{
		return this.invocable.factory();
	}

	@Override public NumericAdapter getAdapter()
	{
		return adapter;
	}

	@Override public <T> T access(InvocationContext context, final NumericAccessor<T> accessor)
	{
		if (!isCompilationContext(context))
			return this.invocable.access(context, accessor);
		
		return adapter.detectType(new NumericAccessor<T>() {

			@Override public T asInteger(IntegerAdapter adapter)
			{
				return accessor.asInteger(adapter);
			}

			@Override public T asLong(LongAdapter adapter)
			{
				return accessor.asLong(adapter);
			}

			@Override public T asDouble(DoubleAdapter adapter)
			{
				return accessor.asDouble(adapter);
			}
		});
	}

	@Override public CompiledInvocable<N> compileAsSubstatement(InvocationContext context)
	{
		if (!isCompilationContext(context))
			return this.invocable.compileAsSubstatement(context);
		return this;
	}

	@Override public Invocable<?> getInvocableParam(int index)
	{
		return this.invocable.getInvocableParam(index);
	}

	public static StringBuilder toString(StringBuilder output, NumericAdapter adapter, ValueType ignoreType)
	{
		FunctorInstance fi = adapter.getFunctorInstance();
		ValueType rt = null;
		if (fi != null) {
			output.append(fi.getFunctor().getName());
			FunctorMethod mi = fi.getMethodInfo();
			if (mi != null) {
				rt = mi.result();
				if (ignoreType == null || ignoreType != rt) {
					output.append(':');
					output.append(rt);
				}
			}
		} else {
			output.append('?');
		}
		output.append('(');
		int idxParam = 0;
		while (true) {
			NumericAdapter p = adapter.getParam(idxParam);
			if (p == null) break;
			if (idxParam > 0)
				output.append(", ");
			toString(output, p, rt);
			idxParam++;
		}
		if (idxParam == 0) {
			output.append(adapter.toString());
		}
		output.append(')');
		return output;
	}

	@Override public String toString()
	{
		return toString(new StringBuilder(), adapter, null).toString();
	}
}